package com.caosy.grassthinkremote.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.MD5;
import com.caosy.grassthinkremote.common.BaseContext;
import com.caosy.grassthinkremote.common.GtrConstant;
import com.caosy.grassthinkremote.common.RespData;
import com.caosy.grassthinkremote.entity.User;
import com.caosy.grassthinkremote.entity.vo.UserReqVo;
import com.caosy.grassthinkremote.mapper.UserMapper;
import com.caosy.grassthinkremote.util.BaseUtil;
import com.caosy.grassthinkremote.util.JwtUtil;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class UserService {
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Resource
    private UserMapper userMapper;

    @Autowired
    private BaseUtil baseUtil;

    public RespData loginAuth(UserReqVo req, HttpServletRequest request) {
        Date now = new Date();
        try {
            if (!isValidAccount(req.getUsername(), req.getPassword())) return RespData.getFailure("登录失败！");
            String token = generateTokenAndSetRedis(req);
            //验证通过，更新登录时间
            Long userId = baseUtil.getCurUserId(request);
            userMapper.updateLoginTime(userId,now);
            return RespData.getSuccess(token);
        } catch (Exception e) {
            //throw new RuntimeException(e);
            System.out.println(e);
            return RespData.getFailure("系统异常");
        }
    }

    /**
     * 根据入参生成token，也放入ThreadLocal，并放入redis
     * @param req
     * @return
     * @throws Exception
     */
    private String generateTokenAndSetRedis(UserReqVo req) throws Exception {
        jwtUtil.setKey();
        String token = jwtUtil.generateToken(req.getUsername());
        //将token进行md5加密
        String md5TokenKey = GtrConstant.REDIS_TOKEN_KEY_PREFIX + MD5.create().digestHex16(token);
        //ThreadLocal中存放username和MD5加密后的token（这里不做，等到鉴权的时候再去存放，直接放redis）
        BaseContext.set(GtrConstant.LOCAL_USERNAME_KEY,req.getUsername());
        redisTemplate.opsForValue().set(md5TokenKey,req.getUsername(),24L, TimeUnit.HOURS);
        return token;
    }

    /**
     * 判断是否是有效账户，先判断是否是初始管理员，再去redis中判断是否存在这个账户
     * @param username
     * @param password
     * @return
     */
    public boolean isValidAccount(String username, String password) {
        //if (isValidAdmin(username, password)) return true;

        /*//不是系统管理员，则去redis中查是否有这个人
        List<User> userList = (List<User>)redisTemplate.opsForValue().get(GtrConstant.REDIS_QUERY_USER_LIST_KEY);
        if (CollUtil.isEmpty(userList)) userList = CollUtil.newArrayList();
        //将userList做成username和password的
        Map<String, String> username2password = userList.stream().collect(Collectors.toMap(User::getUsername, User::getPassword));
        */

        //将password加密
        String encodedPw = MD5.create().digestHex16(password);
        //去数据库中根据user和password查询对应的用户
        List<User> users = userMapper.getUserByNameAndPw(username,encodedPw);

        //传入的用户名和密码在数据库中是否有
        return CollUtil.isNotEmpty(users);
    }

    /**
     * 验证是否是初始管理员
     * @deprecated
     * @param username
     * @param password
     * @return
     */
    @Deprecated
    private boolean isValidAdmin(String username, String password) {
        //去数据库中根据username查询user实体

        String queryUserName = GtrConstant.ADMIN_USERNAME;
        String queryPassword = MD5.create().digestHex16(GtrConstant.ADMIN_PASSWORD);

        User queryUser = User.builder()
                .username(username)
                .password(queryPassword)
                .role(User.RoleEnum.ADMIN.roleType)
                .createTime(new DateTime())
                .updateTime(new DateTime())
                .lastLoginTime(new DateTime())
                .build();

        userMapper.insert(queryUser);

        //匹配传过来的password与数据库中查出来密码的密文是否匹配
        return passwordEncoder.matches(password,queryUser.getPassword());
    }

    /**
     * 管理员添加用户
     * @param req
     * @return
     */
    public RespData adminAddUser(UserReqVo req, HttpServletRequest request) {
        //判断是否是管理员的username
        if (!curUserIsAdmin(request)) return RespData.getFailure("无权限");
        //是管理员，则添加
        User user = req.toUserEntity();
        //分布式锁上锁
        RLock rlock = redissonClient.getLock(GtrConstant.REDISSON_ADD_USER_LOCK);
        boolean locked = false;
        try {
            locked = rlock.tryLock(10,10,TimeUnit.SECONDS);
            addUser(user);
        } catch (Exception e) {
            return RespData.getFailure("添加用户失败：" + e.getMessage());
        } finally {
            if (locked && rlock.isHeldByCurrentThread()) rlock.unlock();
        }
        return RespData.getSuccess("添加成功！");
    }

    /**
     * 添加一个用户（现在是向redis中添加）
     * @param user
     */
    private void addUser(User user) {
        //查询现有用户list
        List<User> userList = (List<User>)redisTemplate.opsForValue().get(GtrConstant.REDIS_QUERY_USER_LIST_KEY);
        if (CollUtil.isEmpty(userList)) userList = CollUtil.newArrayList();
        //判断现有userList中是否有这个user
        List<String> usernameList = userList.stream().map(User::getUsername).collect(Collectors.toList());
        if (usernameList.contains(user.getUsername())) throw new RuntimeException("不可重复添加用户！");
        userList.add(user);
        redisTemplate.opsForValue().set(GtrConstant.REDIS_QUERY_USER_LIST_KEY,userList);
    }

    /**
     * 当前用户是否是系统管理员
     * @param request
     * @return
     */
    private boolean curUserIsAdmin(HttpServletRequest request) {
        //获取请求头的token，并组装成为redis的key，去redis查对应的username
        String authorization = request.getHeader("Authorization");
        String queryRedisKey = GtrConstant.REDIS_TOKEN_KEY_PREFIX + MD5.create().digestHex16(authorization);
        String username = (String) redisTemplate.opsForValue().get(queryRedisKey);
        return StrUtil.equals(GtrConstant.ADMIN_USERNAME,username);
    }
}
